package edu.mcw.rgd.clinminer.web.utils;

import com.vaadin.ui.AbstractField;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractSelect.NewItemHandler;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.Field;
import com.vaadin.ui.PopupDateField;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.TwinColSelect;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.vaadin.addon.customfield.beanfield.BeanFieldWrapper;
import org.vaadin.addon.customfield.beanfield.BeanSetFieldWrapper;

import com.vaadin.addon.jpacontainer.JPAContainer;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.util.BeanItemContainer;

import edu.mcw.rgd.clinminer.domain.Participant;
import edu.mcw.rgd.clinminer.domain.Study;
import edu.mcw.rgd.clinminer.domain.enums.Gender;
import edu.mcw.rgd.clinminer.domain.enums.RelationshipType;
import edu.mcw.rgd.clinminer.domain.view.Annotation;
import edu.mcw.rgd.clinminer.domain.view.JPQLDocumentSuggestionResult;
import edu.mcw.rgd.clinminer.domain.view.JPQLOntologySuggestionResult;
import edu.mcw.rgd.clinminer.web.EntityProviderUtil;

public enum FieldConfiguration {
		annotatedText(
			TextAreaEx.class,
			"Annotated Text",
			"...annotated text",
			"c-caption-required"),
		description(
			TextAreaEx.class,
			"Description",
			"...description",
			null),
		participant(
			ComboBoxEx.class,
			"Participant",
			"...participant",
			"c-caption-required"),
		documentSource(
			ComboBoxEx.class,
			"Document Name",
			"...document name",
			"c-caption-required"),
		documentSourceDate(
			PopupDateFieldEx.class,
			"Document Date",
			"...document date (MM/dd/yyyy)",
			null),
		directObservation(
			CheckBoxEx.class,
			"Direct Observation",
			null,
			null),
		eventDate(
			PopupDateFieldEx.class,
			"Event Date",
			"...event date (MM/dd/yyyy)",
			null),
		exactDate(
			CheckBoxEx.class,
			"Exact Date",
			null,
			null),
		LOINCID(
			TextFieldEx.class,
			"LOINC ID",
			"...LOINC ID",
			null),
		LOINCLabel(
			ComboBoxEx.class,
			"LOINC Label",
			"...LOINC label",
			null),
		ontologyTermID(
			TextFieldEx.class,
			"Ontology Term ID",
			"...phenotype (SNOMED CT) or drug (RxNorm)",
			null),
		ontologyTermLabel(
			ComboBoxEx.class,
			"Ontology Term Label",
			"...ontology term label",
			null),
		value(
			TextFieldEx.class,
			"Value",
			"...measurement value",
			null),
		unit(
			TextFieldEx.class,
			"Unit",
			"...measurement unit",
			null),
		dailyDosage(
			TextFieldEx.class,
			"Daily Dosage",
			"...amount of drug per day",
			null),
		doseForm(
			TextFieldEx.class,
			"Dose Form",
			"...form of drug",
			null),
		days(
			TextFieldEx.class,
			"Days",
			"...days prescribed",
			null),
		reference(
			TextFieldEx.class,
			"Reference",
			"...normal/reference range",
			null),
		testedPhenotypeID(
			TextFieldEx.class,
			"Tested Phenotype",
			"...SNOMED ID",
			null),
		testedPhenotypeLabel(
			TextFieldEx.class,
			"Tested Phenotype Label",
			"...SNOMED Label",
			null),
		notes(
			TextAreaEx.class,
			"Notes",
			"...additional commments",
			null),
		name(
			TextFieldEx.class,
			"Name",
			"...user-friendly identifier",
			null),
		studies(
			TwinColSelect.class,
			"Studies",
			null,
			null),
		firstName(
			TextFieldEx.class,
			"First Name",
			"...first name",
			"c-caption-required"),
		lastName(
			TextFieldEx.class,
			"Last Name",
			"...last name",
			"c-caption-required"),
		gender(
			ComboBoxEx.class,
			"Gender",
			"...gender",
			null),
		DOB(
			PopupDateFieldEx.class,
			"Date of Birth",
			"...date of birth (MM/dd/yyyy)",
			null),
		MRN(
			TextFieldEx.class,
			"MRN",
			"...medical record number",
			null),
		SSN(
			TextFieldEx.class,
			"SSN",
			"...social security number",
			null),
		participants(
			TwinColSelect.class,
			"Participants",
			null,
			null),
		source(
			ComboBoxEx.class,
			"Source",
			"...participant",
			"c-caption-required"),
		target(
			ComboBoxEx.class,
			"Target",
			"...participant",
			"c-caption-required"),
		relationshipType(
			ComboBoxEx.class,
			"Relationship Type",
			"...releationship type",
			"c-caption-required");

	private final Class<?> type;
	private final String caption;
	private final String prompt;
	private final String style;
	private static final String FIELD_WIDTH = "321px";
	private JPAContainer<Participant> containerForParticipants;
	private JPAContainer<Study> containerForStudies;
	private BeanItemContainer<Gender> containerForGenders;
	private BeanItemContainer<RelationshipType> containerForRelationshipTypes;
	private static final Logger log = Logger
			.getLogger(FieldConfiguration.class);
	private static TextFieldEx fieldOntologyId;
	private static TextFieldEx fieldLOINCId;

	FieldConfiguration(Class<?> type, String caption, String prompt,
			String style) {
		this.type = type;
		this.caption = caption;
		this.prompt = prompt;
		this.style = style;

	}

	/**
	 * This creates a new instance of the required type and dispatches to the
	 * correct configuration method based on class type
	 *
	 * @return correctly configured field
	 */
	public Field create() {
		// instantiate appropriate object
		Field o;
		try {
			o = (Field) type.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}

		// configure based on type
		if (this == participant || this == source || this == target) {
			return buildParticipantCombo((ComboBox) o, caption, prompt, style);
		} else if (this == studies) {
			return buildStudiesTwinColSelect((TwinColSelect) o, caption,
					prompt, style);
		} else if (this == gender) {
			return buildGenderCombo((ComboBox) o, caption, prompt, style);
		} else if (this == relationshipType) {
			return buildRelationshipTypeCombo((ComboBox) o, caption, prompt,
					style);
		} else if (this == documentSource) {
			return buildDocumentSourceCombo((ComboBox) o, caption, prompt,
					style);
		} else if (this == participants) {
			return buildParticipantsTwinColSelect((TwinColSelect) o, caption,
					prompt, style);
		} else if (this == ontologyTermLabel) {
			return buildOntologyLabelCombo((ComboBox) o, caption, prompt, style);
		} else if (this == LOINCLabel) {
			return buildLOINCLabelCombo((ComboBox) o, caption, prompt, style);
		} else if (o instanceof TextArea) {
			configureTextArea((TextArea) o, caption, prompt, style);
		} else if (o instanceof ComboBox) {
			configureComboBox((ComboBox) o, caption, prompt, style);
		} else if (o instanceof TextField) {
			configureTextField((TextField) o, caption, prompt, style);
		} else if (o instanceof PopupDateField) {
			configurePopupDateField((PopupDateField) o, caption, prompt, style);
		} else if (o instanceof CheckBox) {
			configureCheckBox((CheckBox) o, caption, prompt, style);
		} else {
			throw new UnsupportedOperationException(
					"Looks like FieldFactory was expecting a different type!");
		}

		// focus
		if (this == name) {
			((Focusable) o).focus();
		}
		if (this == annotatedText) {
			((Focusable) o).focus();
		}

		// keep referene to ontology id field
		// so it can be set from autosuggest
		// in ontology term label
		if (this == ontologyTermID) {
			fieldOntologyId = (TextFieldEx) o;
			o.setTabIndex(-1);
		}
		if (this == LOINCID) {
			fieldLOINCId = (TextFieldEx) o;
			o.setTabIndex(-1);
		}

		return o;
	}

	private Field buildParticipantCombo(ComboBox field, String caption,
			String prompt, String style) {
		field.setContainerDataSource(getContainerForParticipants());
		field.setItemCaptionPropertyId("name");
		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");

		Field wrapped = new BeanFieldWrapper<Participant>(field,
				Participant.class, getContainerForParticipants(), "id");
		wrapped.setCaption(caption);
		wrapped.addStyleName(style);
		// NB: since java passes by reference modifying the object
		// inside leads to unintended consequences
		// need to return a new object here
		return wrapped;
	}

	private Field buildGenderCombo(ComboBox field, String caption,
			String prompt, String style) {
		field.setContainerDataSource(getContainerForGenders());
		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.addStyleName(style);
		field.setCaption(caption);
		return field;
	}

	private Field buildRelationshipTypeCombo(ComboBox field, String caption,
			String prompt, String style) {
		field.setContainerDataSource(getContainerForRelationshipTypes());
		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.addStyleName(style);
		field.setCaption(caption);
		return field;
	}

	private Field buildDocumentSourceCombo(final ComboBox field,
			String caption, String prompt, String style) {
		final List<String> captions = new ArrayList<String>();
		for (JPQLDocumentSuggestionResult item : Annotation
				.getDocumentComboValues()) {
			field.addItem(item.getId());
			field.setItemCaption(item.getId(), item.getCaption());
			captions.add(item.getCaption());
		}

		field.setNewItemsAllowed(true);
		field.setNewItemHandler(new NewItemHandler() {

			@Override
			public void addNewItem(String newItemCaption) {

				if (!captions.contains(newItemCaption)) {
					field.addItem(newItemCaption);
					captions.add(newItemCaption);
					field.setItemCaption(newItemCaption, newItemCaption);
					field.setValue(newItemCaption);
				}
			}
		});

		field.addListener(new ComboBox.ValueChangeListener() {

			@Override
			public void valueChange(ValueChangeEvent event) {
				// clean all captions
				// update this one so that user doesn't see the item count
				// on blur any more
				for (JPQLDocumentSuggestionResult item : Annotation
						.getDocumentComboValues()) {
					if (item.getId().equals(event.getProperty().toString())) {
						field.setItemCaption(event.getProperty().toString(),
								event.getProperty().toString());
					} else {
						field.setItemCaption(item.getId(), item.getCaption());
					}
				}

			}
		});

		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.addStyleName(style);
		field.setCaption(caption);
		field.setFilteringMode(AbstractSelect.Filtering.FILTERINGMODE_CONTAINS);

		return field;
	}

	private Field buildOntologyLabelCombo(final ComboBox field, String caption,
			String prompt, String style) {
		final List<String> captions = new ArrayList<String>();
		final Map<String, String> ontologyIds = new HashMap<String, String>();
		for (JPQLOntologySuggestionResult item : Annotation
				.getOntologyValues()) {
			field.addItem(item.getLabel());
			field.setItemCaption(item.getLabel(), item.getCaption());
			captions.add(item.getCaption());
			ontologyIds.put(item.getLabel(), item.getId());
		}

		field.setNewItemsAllowed(true);
		field.setNewItemHandler(new NewItemHandler() {

			@Override
			public void addNewItem(String newItemCaption) {

				if (!captions.contains(newItemCaption)) {
					field.addItem(newItemCaption);
					captions.add(newItemCaption);
					field.setItemCaption(newItemCaption, newItemCaption);
					field.setValue(newItemCaption);
				}
			}
		});

		field.addListener(new ComboBox.ValueChangeListener() {

			@Override
			public void valueChange(ValueChangeEvent event) {
				// clean all captions
				// update this one so that user doesn't see the item count
				// on blur any more
				for (JPQLOntologySuggestionResult item : Annotation
						.getOntologyValues()) {
					if (item.getLabel().equals(event.getProperty().toString())) {
						field.setItemCaption(event.getProperty().toString(),
								event.getProperty().toString());
					} else {
						field.setItemCaption(item.getLabel(), item.getCaption());
					}
				}
				if (fieldOntologyId != null) {
					fieldOntologyId.setValue(ontologyIds.get(event
							.getProperty().toString()));
				}

			}
		});

		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.addStyleName(style);
		field.setCaption(caption);
		field.setFilteringMode(AbstractSelect.Filtering.FILTERINGMODE_CONTAINS);

		return field;
	}

	private Field buildLOINCLabelCombo(final ComboBox field, String caption,
			String prompt, String style) {
		final List<String> captions = new ArrayList<String>();
		final Map<String, String> ontologyIds = new HashMap<String, String>();
		for (JPQLOntologySuggestionResult item : Annotation
				.getLOINCValues()) {
			field.addItem(item.getLabel());
			field.setItemCaption(item.getLabel(), item.getCaption());
			captions.add(item.getCaption());
			ontologyIds.put(item.getLabel(), item.getId());
		}

		field.setNewItemsAllowed(true);
		field.setNewItemHandler(new NewItemHandler() {

			@Override
			public void addNewItem(String newItemCaption) {

				if (!captions.contains(newItemCaption)) {
					field.addItem(newItemCaption);
					captions.add(newItemCaption);
					field.setItemCaption(newItemCaption, newItemCaption);
					field.setValue(newItemCaption);
				}
			}
		});

		field.addListener(new ComboBox.ValueChangeListener() {

			@Override
			public void valueChange(ValueChangeEvent event) {
				// clean all captions
				// update this one so that user doesn't see the item count
				// on blur any more
				for (JPQLOntologySuggestionResult item : Annotation
						.getLOINCValues()) {
					if (item.getLabel().equals(event.getProperty().toString())) {
						field.setItemCaption(event.getProperty().toString(),
								event.getProperty().toString());
					} else {
						field.setItemCaption(item.getLabel(), item.getCaption());
					}
				}
				if (fieldLOINCId != null) {
					fieldLOINCId.setValue(ontologyIds.get(event
							.getProperty().toString()));
				}

			}
		});

		field.setInputPrompt(prompt);
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.addStyleName(style);
		field.setCaption(caption);
		field.setFilteringMode(AbstractSelect.Filtering.FILTERINGMODE_CONTAINS);

		return field;
	}

	private Field buildStudiesTwinColSelect(TwinColSelect field,
			String caption, String prompt, String style) {
		configureField(field, caption, style);
		field.setLeftColumnCaption("Available:");
		field.setRightColumnCaption("Assigned:");
		field.setMultiSelect(true);
		field.setContainerDataSource(getContainerForStudies());
		field.setItemCaptionPropertyId("name");
		field.setNewItemsAllowed(false);
		Field wrapped = new BeanSetFieldWrapper<Study>(field, Study.class,
				getContainerForStudies(), "Id");
		wrapped.setCaption(caption);
		wrapped.addStyleName(style);
		return wrapped;
	}

	private Field buildParticipantsTwinColSelect(TwinColSelect field,
			String caption, String prompt, String style) {
		configureField(field, caption, style);
		field.setLeftColumnCaption("Available:");
		field.setRightColumnCaption("Assigned:");
		field.setMultiSelect(true);
		field.setContainerDataSource(getContainerForParticipants());
		field.setItemCaptionPropertyId("name");
		field.setNewItemsAllowed(false);
		Field wrapped = new BeanSetFieldWrapper<Participant>(field,
				Participant.class, getContainerForParticipants(), "Id");
		wrapped.setCaption(caption);
		wrapped.addStyleName(style);
		return wrapped;
	}

	private void configureField(AbstractField field, String caption,
			String style) {
		field.setImmediate(true);
		field.setWidth(FIELD_WIDTH);
		field.setHeight("-1px");
		field.setCaption(caption);
		field.addStyleName(style);
	}

	private void configureCheckBox(CheckBox field, String caption,
			String prompt, String style) {
		configureField(field, caption, style);
		// override width setting for checkbox
		field.setWidth("-1px");
	}

	private void configurePopupDateField(PopupDateField field, String caption,
			String prompt, String style) {
		configureField(field, caption, style);
		field.setInputPrompt(prompt);
		field.setDateFormat("MM/dd/yyyy");
		field.setResolution(PopupDateField.RESOLUTION_DAY);
		// field.setLocale(LocaleContextHolder.getLocale());
		field.setInvalidAllowed(true);
		// field.setInvalidAllowed(false);
	}

	private void configureTextField(TextField field, String caption,
			String prompt, String style) {
		configureField(field, caption, style);
		field.setNullRepresentation("");
		field.setInputPrompt(prompt);
	}

	private void configureTextArea(TextArea field, String caption,
			String prompt, String style) {
		configureField(field, caption, style);
		field.setInputPrompt(prompt);
		field.setRows(4);
		field.setNullRepresentation("");
	}

	private void configureComboBox(ComboBox field, String caption,
			String prompt, String style) {
		configureField(field, caption, style);
		field.setInputPrompt(prompt);
	}

	private JPAContainer<Participant> getContainerForParticipants() {
		if (containerForParticipants == null) {
			JPAContainer<Participant> container = new JPAContainer<Participant>(
					Participant.class);
			container.setEntityProvider(EntityProviderUtil.get()
					.getEntityProvider(Participant.class));
			containerForParticipants = container;
		}
		return containerForParticipants;
	}

	private JPAContainer<Study> getContainerForStudies() {
		if (containerForStudies == null) {
			JPAContainer<Study> container = new JPAContainer<Study>(Study.class);
			container.setEntityProvider(EntityProviderUtil.get()
					.getEntityProvider(Study.class));
			containerForStudies = container;
		}
		return containerForStudies;
	}

	private BeanItemContainer<Gender> getContainerForGenders() {
		if (containerForGenders == null) {
			Collection<Gender> items = Arrays.asList(Gender.class
					.getEnumConstants());
			BeanItemContainer<Gender> container = new BeanItemContainer<Gender>(
					Gender.class, items);
			containerForGenders = container;
		}
		return containerForGenders;
	}

	private BeanItemContainer<RelationshipType> getContainerForRelationshipTypes() {
		if (containerForRelationshipTypes == null) {
			Collection<RelationshipType> items = Arrays
					.asList(RelationshipType.class.getEnumConstants());
			BeanItemContainer<RelationshipType> container = new BeanItemContainer<RelationshipType>(
					RelationshipType.class, items);
			containerForRelationshipTypes = container;
		}
		return containerForRelationshipTypes;
	}

}